﻿#region Imports

using System.Reflection;
using FluentNHibernate;
using FluentNHibernate.Conventions;
using NHibernate;
using HibernateConfig = NHibernate.Cfg;
using NHibernate.Context;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Automapping;
using NHibernate.ByteCode.Castle;
using StructureMap;
using StructureMap.Configuration.DSL;
using StructureMap.Graph; 
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Conventions.Helpers;

#endregion

namespace NMA.Infrastructure.NHibernate.Bootstrapper
{
    #region internal imports

    using NMA.Domain.Model;
    using NMA.Infrastructure.UnitOfWork;
    using NMAConfig = NMA.Infrastructure.Configuration;
    using NMA.Infrastructure.NHibernate.UnitOfWork;
    using NMA.Infrastructure.NHibernate.DomainObject;
    using NMA.Infrastructure.NHibernate.Repository;
    using NMA.Infrastructure.NHibernate.Mapping.Convention;
    using NMA.Infrastructure.NHibernate.Mapping.Override;
    using NMA.Infrastructure.NHibernate.Mapping;
    using NMA.Domain.Model.Repository;
    using NMA.Infrastructure.EventAggregator;
    using NMA.Infrastructure.NHibernate.Service.EventAggregator;
    using NMA.Infrastructure.NHibernate.Service.Handler;
    using NMA.Infrastructure.NHibernate.Service.Event;
    using NMA.Domain.Shared.Specification;
    using NMA.Infrastructure.NHibernate.Service.Specification;
    using NMA.Infrastructure.Bootstrapper;
    using NMA.Infrastructure.Security;
    using NMA.Infrastructure.NHibernate.CriteriaExpression;
    using NMA.Infrastructure.NHibernate.OrderByExpression;
    using NMA.Infrastructure.NHibernate.CriteriaExpression.Category.SearchCategory;

    #endregion

    /// <summary>
    /// http://fnhsamples.codeplex.com/
    /// http://blog.vuscode.com/malovicn/archive/2009/11/04/fluent-nhibernate-samples-auto-mapping-part-1-2.aspx
    /// http://stackoverflow.com/questions/704635/how-do-i-override-a-conventions-cascade-rule-in-fluent-nhibernate
    /// http://hadihariri.com/blogengine/post/2008/12/19/Fluent-NHibernate.aspx
    /// http://hadihariri.com/blogengine/post/2009/03/30/Spending-time-with-Fluent-NHibernate-%28Part-2%29-Our-first-Relationship.aspx
    /// http://hadihariri.com/blogengine/post/2008/12/22/Multiple-Value-types-with-Fluent-NHibernates-AutoPersistenceModel.aspx
    /// http://github.com/paulbatum
    /// </summary>
    public class CoreRegistry : Registry 
    {
        public CoreRegistry(NMAConfig.IConfigurationManager configurationManager, string conStringName, params string[] bootTaskAssemblies)
        {
            try
            {
                RegisterFluentHibernateInstance(configurationManager, conStringName);

                RegisterInstances(bootTaskAssemblies);
            }
            catch (System.Exception ex)
            {
                throw ex;
            }
        }

        private void RegisterFluentHibernateInstance(NMAConfig.IConfigurationManager configurationManager, string conStringName)
        {
            configurationManager.SetConnectionStringName(conStringName);
            var providerName = configurationManager.ProviderName;
            var connectionString = configurationManager.ConnectionString;

            var cfg = Fluently.Configure()
            .Database(MsSqlConfiguration
                        .MsSql2005
                        .ConnectionString(connectionString))
            .ExposeConfiguration(c => c.SetProperty(HibernateConfig.Environment.ReleaseConnections, "on_close"))
            .ExposeConfiguration(c => c.SetProperty(HibernateConfig.Environment.ProxyFactoryFactoryClass, typeof(ProxyFactoryFactory).AssemblyQualifiedName))
            .ExposeConfiguration(c => c.SetProperty(HibernateConfig.Environment.ShowSql, "true"))
            .ExposeConfiguration(BuildSchema)
            .BuildConfiguration();

            var persistenceModel = new PersistenceModel();

            persistenceModel.AddMappingsFromAssembly(typeof(UserMap).Assembly);
            persistenceModel.AddMappingsFromAssembly(typeof(CategoryMap).Assembly);
            persistenceModel.AddMappingsFromAssembly(typeof(NewsMap).Assembly);
            persistenceModel.AddMappingsFromAssembly(typeof(PollMap).Assembly);
            persistenceModel.AddMappingsFromAssembly(typeof(AuditMap).Assembly);
            persistenceModel.AddMappingsFromAssembly(typeof(ResourceMap).Assembly);
            //persistenceModel.WriteMappingsTo("C:\\NMA");  // we must create the NMA directory or comment this line
            persistenceModel.Configure(cfg);

            var sessionFactory = cfg.BuildSessionFactory();

            For<HibernateConfig.Configuration>().Singleton().Use(cfg);

            For<ISessionFactory>().Singleton().Use(sessionFactory);

            For<ISession>().HybridHttpOrThreadLocalScoped()
                .Use(ctx => ctx.GetInstance<ISessionFactory>().OpenSession());

            For<IUnitOfWork>().HybridHttpOrThreadLocalScoped()
                .Use<UnitOfWork>();

            For<IUnitOfWorkFactory>().Singleton()
                .Use<UnitOfWorkFactory>();
        }

        /// <summary>
        /// Register all instance thet need for StructureMap can resolve it
        /// </summary>
        private void RegisterInstances(params string[] bootTaskAssemblies)
        {
            // create all objects
            For<IDomainObjectFactory>().Use<DomainObjectFactory>();
            For<IAuditFactory>().Use<AuditFactory>();
            // repository
            For<ICategoryRepository>().Use<CategoryRepository>();
            For<INewsRepository>().Use<NewsRepository>();
            For<IPollRepository>().Use<PollRepository>();
            For<IAuditRepository>().Use<AuditRepository>();
            For<IUserRepository>().Use<UserRepository>();
            For<IResourceRepository>().Use<ResourceRepository>();

            // EventAggregator
            For<IEventPublisher>().Use<EventPublisher>();
            For<ISubscriptionService>().Use<EventSubscriptions>();
            For<IConsumer<AuditEvent>>().Use<AuditHandler>();
 
            // specification
            For<ISpecification<AuditEvent>>().Use<CategoryAuditSpecification>();

            // encryptEngine
            For<ICryptography>().Use<StringHasher>();

            // search expression factory
            For<ICriteriaExpressionFactory>().Use<CriteriaExpressionFactory>();

            // order by expression factory
            For<IOrderByExpressionFactory>().Use<OrderByExpressionFactory>();

            // Search finder factory
            For<ISearchFinderFactory>().Use<SearchFinderFactory>();
            For<ISearchChainBuilder>().Use<SearchChainBuilder>();

            // Scan all task that implement IBootstrapperTask interface
            Scan(x => ScanTaskAssemblies(x, bootTaskAssemblies)); 
        }

        private void ScanTaskAssemblies(IAssemblyScanner scanner, params string[] bootTaskAssemblies)
        {
            for (int i = 0; i < bootTaskAssemblies.Length; i++)
            {
                scanner.Assembly(bootTaskAssemblies[i].ToString());
            }

            scanner.AddAllTypesOf<IBootstrapperTask>();
            scanner.WithDefaultConventions();
        }

        /// <summary>
        /// Build database schema from configuration
        /// </summary>
        /// <param name="config">configuration that built from my settings</param>
        private static void BuildSchema(HibernateConfig.Configuration config)
        {
            //new SchemaExport(config).Create(true, true);
        }
    }
}